export type ValueChangedListener<T> = (sender: T, prop: string, value: any, oldValue?: any) => void;

export class PropChanged<TSelf extends PropChanged<any, any>, TValues> {
  private _changeEvents: ValueChangedListener<TSelf>[] = [];

  protected afterSetProp?(prop: any, value: any): void;

  setProp<P extends keyof TValues>(prop: P, value: TValues[P], emitChange = true) {
    const oldValue = (this as any as TValues)[prop];
    (this as any as TValues)[prop] = value;
    this.afterSetProp?.(prop, value);
    if (emitChange) {
      this._emitValueChanged(prop as string, value, oldValue);
    }
  }

  protected _emitValueChanged(prop: string, value: any, oldValue?: any) {
    this._changeEvents.forEach(listener => listener(this as any as TSelf, prop, value, oldValue));
  }

  onValueChanged(listener: ValueChangedListener<TSelf>) {
    this._changeEvents.push(listener);
  }

  offValueChanged(listener: ValueChangedListener<TSelf>) {
    this._changeEvents = this._changeEvents.filter(x => x !== listener);
  }
}
